/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.inspur.edp.metadata.service.dev.core.manager;

import com.inspur.edp.lcm.metadata.api.entity.MetadataHeader;
import com.inspur.edp.lcm.metadata.api.entity.MetadataProject;
import com.inspur.edp.lcm.metadata.api.exception.DuplicateImplementationException;
import com.inspur.edp.lcm.metadata.api.exception.ErrorCodes;
import com.inspur.edp.lcm.metadata.api.i18n.LcmMetadataDevLog;
import com.inspur.edp.lcm.metadata.api.i18n.LogCodes;
import com.inspur.edp.lcm.metadata.common.configuration.MetadataTypeHelper;
import com.inspur.edp.lcm.metadata.core.MetadataProjectCoreService;
import com.inspur.edp.lcm.metadata.core.entity.MdProjectDependencyNode;
import com.inspur.edp.lcm.metadata.core.index.ProjectMetadataCacheService;
import com.inspur.edp.lcm.metadata.spi.MetadataElementService;
import com.inspur.edp.lcm.metadata.spi.MetadataElementValidator;
import com.inspur.edp.lcm.metadata.spi.entity.MetadataDependencyDetail;
import com.inspur.edp.lcm.metadata.spi.entity.MetadataElement;
import com.inspur.edp.lcm.metadata.spi.entity.MetadataElementDependencyDetail;
import com.inspur.edp.lcm.metadata.spi.entity.MetadataElementLocator;
import com.inspur.edp.lcm.metadata.spi.entity.ValidateResult;
import com.inspur.edp.lcm.metadata.spi.entity.ValidationLevelEnum;
import com.inspur.edp.metadata.service.dev.core.cache.MetadataRelationGraphCache;
import com.inspur.edp.metadata.service.dev.core.entity.GspMetadataCache;
import com.inspur.edp.metadata.service.dev.core.entity.validate.MetadataDirectGraph;
import com.inspur.edp.metadata.service.dev.core.entity.validate.MetadataRelateEdge;
import com.inspur.edp.metadata.service.dev.core.entity.validate.MetadataValidateFailure;
import com.inspur.edp.metadata.service.dev.core.entity.validate.MetadataValidateFailureDetail;
import com.inspur.edp.metadata.service.dev.core.entity.validate.MetadataValidateResult;
import com.inspur.edp.metadata.service.dev.core.entity.validate.MetadataVertex;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 元数据关系校验
 *
 * @author sunhongfei
 */
@Slf4j
public class MetadataValidateManager {

    private final MetadataProjectCoreService metadataProjectCoreService;

    public MetadataValidateManager() {
        this.metadataProjectCoreService = MetadataProjectCoreService.getCurrent();
    }

    /**
     * 生成指定应用下的元数据关系图
     *
     * @param boAbsolutePath 应用绝对路径
     * @return 元数据关系图
     */
    public MetadataDirectGraph generateMetadataGraphInBo(String boAbsolutePath) {
        // 获取BO下所有元数据工程列表
        List<String> projectsInBo = metadataProjectCoreService.getProjectsInBo(boAbsolutePath);
        // 若BO下不存在工程,则缓存中设置为空图
        if (CollectionUtils.isEmpty(projectsInBo)) {
            MetadataRelationGraphCache.evict(boAbsolutePath);
            return null;
        }
        // 获取缓存中存在的图
        MetadataDirectGraph gspMetadataListGraph = MetadataRelationGraphCache.get(boAbsolutePath);

        // 获取图中所有节点(用于记录待删除节点)
        Set<String> vertexToDel = gspMetadataListGraph.getVertexMetadataIds();

        // 遍历各个工程路径, 获取工程下的元数据文件 并构造入图
        for (String projectPath : projectsInBo) {
            MetadataProject metadataProject = metadataProjectCoreService.getMetadataProjInfo(projectPath);
            // 获取工程的工程依赖关系, 用于解析元数据的依赖
            Map<String, MdProjectDependencyNode> metadataPackageNodes = ProjectMetadataCacheService.getNewInstance(metadataProject, projectPath).getMetadataPackageNodes();
            // 获取工程下所有元数据(缓存结构,需要其文件变更时间)
            List<GspMetadataCache> metadataCacheListInProject = MetadataInProjectManager.getMetadataCacheListInProject(projectPath);

            // 若工程下没有元数据,则跳过
            if (CollectionUtils.isEmpty(metadataCacheListInProject)) {
                continue;
            }

            // 遍历工程下元数据列表
            for (GspMetadataCache metadataFile : metadataCacheListInProject) {
                // 获取元数据id
                String metadataId = metadataFile.getMetadataId();
                vertexToDel.remove(metadataId);
                // 根据标签(元数据id)在图中查询元数据对应节点
                MetadataVertex vertex = gspMetadataListGraph.getVertex(metadataId);

                if (vertex != null && vertex.getContent() != null) {
                    // 缓存存在
                    GspMetadataCache metadataCache = vertex.getContent();

                    // 若时间一致, 则不做变更
                    if (metadataCache.getCacheModifyTime() == metadataFile.getCacheModifyTime()) {
                        continue;
                    }
                }
                // 将文件cache作为顶点赋予
                gspMetadataListGraph.addVertex(metadataId, metadataFile);

                // 根据元数据文件中记录的元数据类型,获取对应的元数据服务以解析元数据关系,若不存在服务则不做解析
                String type = metadataFile.getMetadataWithoutContent().getHeader().getType();
                Optional<MetadataElementService> optional = MetadataElementServiceFactory.getMetadataElementService(type);
                if (!optional.isPresent()) {
                    continue;
                }

                MetadataElementService metadataElementService = optional.get();

                // 调用元数据对应服务,解析元数据内容识别元数据关系
                List<MetadataDependencyDetail> metadataDependencyDetails = metadataElementService.getMetadataDependencyDetails(metadataFile.getMetadataWithContent());
                if (CollectionUtils.isEmpty(metadataDependencyDetails)) {
                    continue;
                }
                // 遍历元数据识别到的关系, 图中构造节点的边
                for (MetadataDependencyDetail metadataDependencyDetail : metadataDependencyDetails) {
                    // 判断当前引用元数据是否为BO内引用途径,若非BO内引用的话不构造此元数据关系
                    MdProjectDependencyNode dependencyNode = metadataPackageNodes.get(metadataDependencyDetail.getMetadataId());
                    if (dependencyNode == null || !dependencyNode.isBO()) {
                        continue;
                    }
                    gspMetadataListGraph.addEdge(metadataId, metadataDependencyDetail.getMetadataId(), metadataDependencyDetail);
                }
            }
        }
        // 将图中 待删除节点 移除
        for (String vertexMetadataId : vertexToDel) {
            gspMetadataListGraph.removeVertex(vertexMetadataId);
        }
        return gspMetadataListGraph;
    }

    /**
     * 校验指定应用下的元数据关系
     *
     * @param boAbsolutePath 应用绝对路径
     * @return 校验结果
     */
    public MetadataValidateResult validateMetadatasInBo(String boAbsolutePath) {
        MetadataValidateResult result = new MetadataValidateResult();

        List<MetadataValidateFailure> failureDetails = result.getFailureDetails();

        // 构造应用内元数据关系图
        MetadataDirectGraph graph = generateMetadataGraphInBo(boAbsolutePath);

        Iterator<MetadataVertex> vertexIterator = graph.getVertexIterator();
        // 遍历元数据关系图中的各个顶点
        while (vertexIterator.hasNext()) {
            MetadataVertex sourceVertex = vertexIterator.next();

            GspMetadataCache sourceVertexContent = sourceVertex.getContent();
            String sourceMetadataType = sourceVertexContent.getMetadataWithoutContent().getHeader().getType();

            // 获取顶点为起点的所有边 遍历校验
            Set<MetadataRelateEdge> metadataRelateEdges = sourceVertex.getEdges();
            for (MetadataRelateEdge metadataRelateEdge : metadataRelateEdges) {
                String endMetadataId = metadataRelateEdge.getEndMetadataId();
                MetadataVertex targetVertex = graph.getVertex(endMetadataId);
                if (targetVertex == null) {
                    continue;
                }

                GspMetadataCache targetVertexContent = targetVertex.getContent();
                // 获取目标元数据类型
                String targetMetadataType = targetVertexContent.getMetadataWithoutContent().getHeader().getType();
                // 获取目标元数据对应的元数据服务 用于查询目标元数据的内容节点
                Optional<MetadataElementService> elementServiceOptional = MetadataElementServiceFactory.getMetadataElementService(targetMetadataType);
                // 若目标元数据不存在元数据服务
                if (!elementServiceOptional.isPresent()) {
                    continue;
                }

                MetadataElementService elementService = elementServiceOptional.get();

                // 获取元数据引用详情
                List<MetadataElementDependencyDetail> elementReferenceList = metadataRelateEdge.getContent().getElementReferences();
                // 汇总引用关系中声明的指定元数据内容节点标识集合
                Set<MetadataElementLocator> elementLocators = elementReferenceList.stream().map(MetadataElementDependencyDetail::getTargetElementLocator).collect(Collectors.toSet());
                // 使用目标元数据实现方法, 根据内容节点标识集合 批量获取对应的内容节点信息
                Map<MetadataElementLocator, MetadataElement> metadataElementMap;
                try {
                    metadataElementMap = elementService.getMetadataElement(targetVertexContent.getMetadataWithContent(), elementLocators);
                } catch (Exception e) {
                    String msgBuffer = LcmMetadataDevLog.getInstance().getString(LogCodes.LOG_METADATA_DEV_0015,
                            MetadataTypeHelper.getInstance().getMetadataType(targetMetadataType).getTypeName(),
                            targetVertexContent.getMetadataWithoutContent().getHeader().getName());

                    MetadataValidateFailure metadataValidateFailure = new MetadataValidateFailure();
                    metadataValidateFailure.setMsg(msgBuffer);
                    metadataValidateFailure.getDetail().add(MetadataValidateFailureDetail.error("[ERROR] " + e.getMessage()));
                    failureDetails.add(metadataValidateFailure);
                    continue;
                }

                List<ValidateResult> validateResults = new ArrayList<>();
                // 校验每个关系
                for (MetadataElementDependencyDetail elementReference : elementReferenceList) {
                    MetadataElement sourceElement = elementReference.getSourceElement();
                    MetadataElement targetElement = metadataElementMap.get(elementReference.getTargetElementLocator());

                    Optional<MetadataElementValidator> validatorOptional = MetadataElementValidatorFactory.getMetadataElementValidator(elementReference.getReferenceType());
                    if (!validatorOptional.isPresent()) {
                        continue;
                    }

                    ValidateResult validateResult;
                    try {
                        validateResult = validatorOptional.get().validate(sourceElement, targetElement, sourceVertexContent.getMetadataWithContent(), targetVertexContent.getMetadataWithContent());
                    } catch (Exception e) {
                        ValidateResult exceptionValidResult = new ValidateResult();
                        exceptionValidResult.setValid(false);
                        exceptionValidResult.setLevel(ValidationLevelEnum.ERROR);
                        exceptionValidResult.setMessage(e.getMessage());
                        validateResults.add(exceptionValidResult);
                        continue;
                    }

                    if (!validateResult.isValid()) {
                        validateResults.add(validateResult);
                    }
                }

                // 汇总当前两元数据间依赖关系校验失败原因, 并构造返回结果
                if (!CollectionUtils.isEmpty(validateResults)) {
                    MetadataValidateFailure metadataValidateFailure = new MetadataValidateFailure();

                    String sourceMetadataTypeName = MetadataTypeHelper.getInstance()
                            .getMetadataType(sourceMetadataType).getTypeName();
                    String targetMetadataTypeName = MetadataTypeHelper.getInstance()
                            .getMetadataType(targetMetadataType).getTypeName();

                    MetadataHeader sourceMetadataHeader = sourceVertexContent.getMetadataWithoutContent().getHeader();
                    MetadataHeader targetMetadataHeader = targetVertexContent.getMetadataWithoutContent().getHeader();
                    String formatted = LcmMetadataDevLog.getInstance().getString(LogCodes.LOG_METADATA_DEV_0016,
                            sourceMetadataTypeName, sourceMetadataHeader.getName(), sourceMetadataHeader.getCode(),
                            targetMetadataTypeName, targetMetadataHeader.getName(), targetMetadataHeader.getCode());
                    metadataValidateFailure.setMsg(formatted);

                    List<MetadataValidateFailureDetail> metadataValidateFailureDetailList = validateResults.stream()
                            .map(validateResult ->
                                    new MetadataValidateFailureDetail(validateResult.toString(), validateResult.getLevel()))
                            .collect(Collectors.toList());

                    metadataValidateFailure.getDetail().addAll(metadataValidateFailureDetailList);
                    failureDetails.add(metadataValidateFailure);
                }

            }

        }
        return result;
    }
}

/**
 * 元数据内容节点服务工厂类
 */
class MetadataElementServiceFactory {
    private static final Map<String, MetadataElementService> metadataElementServiceMap = new ConcurrentHashMap<>();

    /**
     * 根据指定的元数据类型获取对应的元数据服务
     *
     * @param metadataType 元数据类型
     * @return 元数据服务实现
     */
    protected static Optional<MetadataElementService> getMetadataElementService(String metadataType) {
        if (CollectionUtils.isEmpty(metadataElementServiceMap)) {
            // 汇总所有的SPI实现类
            Map<String, MetadataElementService> serviceMap = SpringBeanUtils.getApplicationContext().getBeansOfType(MetadataElementService.class);
            Map<String, MetadataElementService> tempMap = new HashMap<>();
            // 根据SPI实现类指定其对应的元数据类型,构造哈希表. 若出现元数据类型重复则抛出异常
            serviceMap.values()
                    .forEach(service -> {
                        MetadataElementService elementService = tempMap.putIfAbsent(service.getMetadataType(), service);
                        if (null != elementService) {
                            throw new DuplicateImplementationException(ErrorCodes.ECP_DUP_IMPL_0001,
                                    "MetadataElementService", "MetadataType", service.getMetadataType(),
                                    service.getClass().getName(), elementService.getClass().getName());
                        }
                    });
            metadataElementServiceMap.putAll(tempMap);
        }
        return Optional.ofNullable(metadataElementServiceMap.get(metadataType));
    }
}

/**
 * 元数据关系校验服务工厂类
 */
class MetadataElementValidatorFactory {
    private static final Map<String, MetadataElementValidator> metadataElementValidatorMap = new ConcurrentHashMap<>();

    /**
     * 根据指定的依赖关系类型获取对应的关系校验服务
     *
     * @param referenceType 依赖关系类型
     * @return 关系校验服务
     */
    protected static Optional<MetadataElementValidator> getMetadataElementValidator(String referenceType) {
        if (CollectionUtils.isEmpty(metadataElementValidatorMap)) {
            // 汇总所有SPI的实现类
            Map<String, MetadataElementValidator> serviceMap = SpringBeanUtils.getApplicationContext().getBeansOfType(MetadataElementValidator.class);
            Map<String, MetadataElementValidator> tempMap = new HashMap<>();
            // 根据SPI实现类声明其适配的依赖关系类型，构造依赖关系类型-实现类的映射哈希表
            serviceMap.values()
                    .forEach(service -> {
                        MetadataElementValidator validator = tempMap.putIfAbsent(service.getReferenceType(), service);
                        // 若同一依赖关系被两次声明，则抛出异常
                        if (null != validator) {
                            throw new DuplicateImplementationException(ErrorCodes.ECP_DUP_IMPL_0001,
                                    "MetadataElementValidator", "ReferenceType", validator.getReferenceType(),
                                    service.getClass().getName(), validator.getClass().getName());
                        }
                    });
            metadataElementValidatorMap.putAll(tempMap);

        }
        return Optional.ofNullable(metadataElementValidatorMap.get(referenceType));
    }

}